#pragma once

#include <string>
#include <vector>
#include <cstdint>
#include <cassert>
#include <zenox/glm.hpp>
#include <zenox/unicode.hpp>
#include <zenox/raiiutils.hpp>

namespace zenoui {

template <class T = glm::u8vec4>
struct Image {
    std::vector<T> m_flatimg;
    zenox::value_initialize_on_move<size_t> m_nx, m_ny;

    using value_type = T;

    Image() = default;

    explicit Image(std::string const &path) {
        load(path);
    }

    void load(std::string const &path);
    void save(std::string const &path, std::string const &type, int quality = 80);

    T &operator()(size_t x, size_t y) {
        return m_flatimg[x + m_nx * y];
    }

    T const &operator()(size_t x, size_t y) const {
        return m_flatimg[x + m_nx * y];
    }

    void downscale() {
        /* zenox::log_warn() << "downscale: " << m_nx << m_ny; */
        Image ret(m_nx / 2, m_ny / 2);
        #pragma omp parallel for collapse(2)
        for (size_t y = 0; y < ret.m_ny; y++) {
            for (size_t x = 0; x < ret.m_nx; x++) {
                ret(x, y) = (*this)(x * 2, y * 2);
            }
        }
        std::swap(ret, *this);
    }

    void resize(size_t nx, size_t ny) {
        m_flatimg.clear();
        m_flatimg.resize(nx * ny);
        m_nx = nx;
        m_ny = ny;
    }

    void resizey(size_t ny) {
        m_flatimg.resize(m_nx * ny);
        m_ny = ny;
    }

    void save(std::string const &path) {
        auto ext = zenox::pathu8(zenox::u8path(path).extension());
        if (ext.empty()) [[unlikely]] {
            ext = "png";
        } else if (ext[0] == '.') [[likely]] {
            ext.erase(0, 1);
        }
        save(path, ext);
    }

    explicit Image(size_t nx, size_t ny)
    : m_flatimg(nx * ny), m_nx(nx), m_ny(ny) {}

    T *data() {
        return m_flatimg.data();
    }

    T const *data() const {
        return m_flatimg.data();
    }

    std::size_t sizex() const {
        return m_nx;
    }

    std::size_t sizey() const {
        return m_ny;
    }

    std::size_t size() const {
        assert(m_nx * m_ny == m_flatimg.size());
        return m_nx * m_ny;
    }
};

}
